package com.qf.controller;

import com.qf.mapper.TicketMapper;
import com.qf.pojo.Ticket;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping("/ticket")
public class TicketController {

    @Autowired
    private TicketMapper ticketMapper;

    @Value("${winnum}")
    private Integer winnum;

    @Autowired
    private StringRedisTemplate redisTemplate;

    private Object lock = new Object();

    private static final String LOCK_PREFIX = "ticket:lock:";
    private static final String LOCK_VALUE_PREFIX = "TICKET:VALUE:";

    @GetMapping("/sell/{id}")
    public void sell(@PathVariable("id") Integer id) throws InterruptedException {

        while (true){
            //加一个分布式锁：(买不同票，不冲突，买相同票才会加锁)
            //设置成功，加锁成功，设置失败，加锁失败（这个方法内部使用的是setnx指令）
            //问题二：当业务没有执行完成，锁超时释放了--解决问题的方式，是给这个锁超时时间续时（开启一个守护线程，当程序中所有线程都是守护线程时，会自动退出）

            //看门狗
            Thread thread = new Thread(()->{
                //续时
                while (true) {
                    Long expire = redisTemplate.getExpire(LOCK_PREFIX + id);
                    //在续时时，要判断，当前业务是否由我负责
                    //获取当前获取锁的窗口,如果当前获取锁的窗口就是我们当前守护线程所在窗口，续时
                    String value = redisTemplate.opsForValue().get(LOCK_PREFIX + id);
                    if (expire != null) {
                        if (expire <= 2 && (LOCK_VALUE_PREFIX + winnum).equals(value)) {
                            redisTemplate.expire(LOCK_PREFIX + id, 3, TimeUnit.SECONDS);
                        }
                    }
                }
            });
            //设置当前线程为守护线程
            thread.setDaemon(true);
            thread.start();

            //问题一：添加过期时间，防止进程非正常退出，锁对象无法释放的问题  (setnx实现的分布式锁，是不可重入的 -- 实现可重入锁，需要使用hash结构)
            //先获取key对应的值，判断值是否是当前进程拿到锁，是将这个value+1 -- lua脚本 （Redisson）
            Boolean isLock = redisTemplate.opsForValue().setIfAbsent(LOCK_PREFIX + id, LOCK_VALUE_PREFIX+winnum,3, TimeUnit.SECONDS);
            if (isLock) {
                Ticket ticket = ticketMapper.selectById(id);
                try {
                    //查询是否有票
                    if (ticket.getCount() > 0) {
                        //有票
                        System.out.println(winnum + "窗口正在卖出第" + ticket.getCount() + "张票");

                        //模拟卖票耗时
                        Thread.sleep(5000);

                        ticket.setCount(ticket.getCount() - 1);

                        //将新的票数设置到数据库
                        ticketMapper.updateById(ticket);

                        System.out.println(winnum + "窗口卖出票后，剩余票数为: " + ticket.getCount());
                    } else {
                        //没票
                        break;
                    }
                }finally {
                    //模拟进程挂掉，让锁无法释放
                    if (winnum == 1 && ticket.getCount()<95){
                        int i = 1/0;
                    }

                    //释放锁
                    redisTemplate.delete(LOCK_PREFIX+id);
                }
            }
        }

    }
}
